package com.example.devecohp;

import com.intellij.diff.chains.DiffRequestChain;
import com.intellij.diff.chains.SimpleDiffRequestChain;
import com.intellij.diff.editor.ChainDiffVirtualFile;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;

import java.util.*;

/**
 * This class control the overall flow of the plugin, it contains list of PageInfo that the auditor created and a
 * ToolWindowInfo to update the ui, each project should have its own PageManager instance
 */
public class PageManager {
    private ArrayList<PageInfo> keeptPages = null;
    private static Map<Project, PageManager> instance;
    private PageInfo selected;
    private List<PageInfo> data;
    private ToolWindowInfo toolWindowInfo;

    /**
     * a singleton method so other classes can easily access it
     * @param project the project for the Page manager
     * @return the Page manager for the input project
     */
    public static PageManager getInstance(Project project){
        if(instance==null){
            instance=new HashMap<>();
        }
        if(!instance.containsKey(project)){
            PageManager result = new PageManager();
            instance.put(project,result);
            addFocus(project);
        }
        return instance.get(project);
    }

    /**
     * @return the toolwindowInfo associate with this project
     */
    public ToolWindowInfo getToolWindowInfo(){
        return this.toolWindowInfo;
    }

    /**
     * @param toolWindowInfo the new toolWindowInfo for this Project
     */
    public void setToolWindowInfo(ToolWindowInfo toolWindowInfo){
        this.toolWindowInfo = toolWindowInfo;
    }

    /**
     * add the event listener that listen to the change in selected page to the project inorder to update the toolwindow
     * accordingly
     * @param project the project the listener will be added
     */
    public static void addFocus(Project project){
        MessageBus bus = project.getMessageBus();

        MessageBusConnection connection = bus.connect();

        connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {

            @Override
            public void selectionChanged(FileEditorManagerEvent event){
                if(event==null){
                    return;
                }

                PageManager pageManager = PageManager.getInstance(project);
                ToolWindowInfo toolWindowInfo = pageManager.getToolWindowInfo();
                PageInfo oldPageInfo = pageManager.getPageInfo(event.getOldFile());
                if(oldPageInfo==null&&event.getOldFile()!=null){
                    PsiFile currFile = PsiManager.getInstance(project).findFile(event.getOldFile());
                    oldPageInfo = pageManager.getPageInfo(currFile.getVirtualFile().getPath());
                }
                if(oldPageInfo!=null){
                    oldPageInfo.save(toolWindowInfo.includeError.isSelected(),toolWindowInfo.includeWarning.isSelected(),toolWindowInfo.includeInfo.isSelected());
                }
                PageInfo pageInfo = pageManager.select(event.getNewFile());
                if(pageInfo==null&&event.getNewFile()!=null){
                    PsiFile currfile = PsiManager.getInstance(project).findFile(event.getNewFile());
                    pageInfo = pageManager.getPageInfo(currfile.getVirtualFile().getPath());
                    if(pageInfo!=null) {
                        pageManager.select(pageInfo.getDiffFile());
                    }
                }
                if(pageInfo!=null){
                    //ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow("HPAuditor");
                    //toolWindow.activate (null);
                    //MyToolWindowFactory toolWindowFactory = new MyToolWindowFactory();
                    MyToolWindowFactory.updateUpperPanel(project);
                    MyToolWindowFactory.filterUpper(project);
                    Auditor.updateDocumentPanel(project, DocumentManager.CHINESE);
                    if(pageManager.isReplaceing()) {
                        toolWindowInfo.reset(PageManager.getInstance(project).getSelected());
                    }
                }


            }
        });
    }

    private PageManager(){
        this.data=new ArrayList<>();
    }

    /**
     * Search for a pageInfo object by the id
     * @param id the id (the original file path) of the pageInfo
     * @return the pageInfo object
     */
    public PageInfo getPageInfo(String id){
        for(PageInfo pageInfo: this.data){
            if(pageInfo.getId().equals(id)){
                return pageInfo;
            }
        }
        return null;
    }

    /**
     * Search for a pageInfo object by its comparison file
     * @param file the comparison file
     * @return the pageInfo object
     */
    public PageInfo getPageInfo(VirtualFile file){
        for(PageInfo pageInfo: this.data){
            VirtualFile diffFile = pageInfo.getDiffFile();
            if(diffFile!=null&&diffFile==file){
                return pageInfo;
            }
        }
        return null;
    }

    /**
     * Select a pageInfo object by its comparison file
     * @param file the comparison file
     * @return the pageInfo object
     */
    public PageInfo select(VirtualFile file){
        if(file==null){
            return null;
        }
        for (PageInfo datum : this.data) {
            if (file.equals(datum.getDiffFile())) {
                this.selected = datum;
                return this.selected;
            }
        }
        return null;
    }

    /**
     * Select a pageInfo object by its Psi file
     * @param file the Psi file of the pageInfo is created on
     * @return the pageInfo object
     */
    public PageInfo select(PsiFile file){
        for (PageInfo datum : this.data) {
            if (datum.oldFile.equals(file)) {
                this.selected = datum;
                return this.selected;
            }
        }
        return null;
    }

    /**
     * generate a new pageInfo project
     * @param project the project the page is in
     * @param newFile the virtual file for the output of the auditor
     * @param oldFile the original file the pageInfo is generated
     * @param auditEntries the rule messages
     * @return the newly created page info
     */
    public PageInfo createNewDiffPage(Project project, VirtualFile newFile, PsiFile oldFile, List<PageInfo.RuleInfo> auditEntries){
        PageInfo pageInfo = getPageInfo(oldFile.getVirtualFile().getPath());
        if(pageInfo==null) {
            pageInfo = new PageInfo(project, newFile, oldFile, auditEntries, isReplaceing());
            data.add(pageInfo);
            selected = pageInfo;
        }else{
        pageInfo.updateInfo(auditEntries,newFile,isReplaceing());

        MyToolWindowFactory.updateUpperPanel(project);
        }
        if(!isReplaceing()){
            keeptPages.add(pageInfo);
        }
        return pageInfo;
    }

    /**
     * get the selected page
     * @return the pageInfo for selected page
     */
    public PageInfo getSelected(){
        return selected;
    }

    public static ChainDiffVirtualFile replacePage(Project project, ChainDiffVirtualFile oldFile, DiffRequest request){
        if(oldFile!=null){
            FileEditorManager.getInstance(project).closeFile(oldFile);
        }
        DiffRequestChain requestChain = new SimpleDiffRequestChain(request);
        ChainDiffVirtualFile result = new ChainDiffVirtualFile(requestChain, DiffBundle.message("label.default.diff.editor.tab.name"));
        FileEditorManager.getInstance(project).openFile(result, true);
        return result;

    }

    /**
     * update the ui to show a pageInfo
     * @param pageInfo the pageInfo to show
     */
    public void replaceWithPage(PageInfo pageInfo){
        if(this.isReplaceing()) {
            this.toolWindowInfo.root.removeAllChildren();
            this.toolWindowInfo.addNodeForPage(pageInfo);
            this.toolWindowInfo.reloadTree();
        }
    }

    /**
     * switch to replace mode for selecting another page info
     */
    public void setModeReplace(){
        this.keeptPages =null;
    }

    /**
     * switch to keep mode for selecting another page info
     */
    public void setModeKeep(){
            this.keeptPages = new ArrayList<>();
    }

    /**
     * @return whether this PageManager is in replace mode
     */
    public boolean isReplaceing(){
        return this.keeptPages ==null;
    }

    /**
     * get the total number of errors
     * @return the total number of errors
     */
    public int  getErrors(){
        if(isReplaceing()){
            if(this.selected==null){
                return 0;
            }
            return this.selected.getErrors();
        }
        int result = 0;
        for(PageInfo p : keeptPages){
            result+=p.getErrors();
        }
        return result;
    }

    /**
     * get the total number of defects
     * @return the total number of defects
     */
    public int  getDefects(){
        if(isReplaceing()){
            if(this.selected==null){
                return 0;
            }
            return this.selected.getDefects();
        }
        int result = 0;
        for(PageInfo p : keeptPages){
            result+=p.getDefects();
        }
        return result;
    }
    /**
     * get the total number of Infos
     * @return the total number of Infos
     */
    public int  getInfos(){
        if(isReplaceing()){
            if(this.selected==null){
                return 0;
            }
            return this.selected.getInfos();
        }
        int result = 0;
        for(PageInfo p : keeptPages){
            result+=p.getInfos();
        }
        return result;
    }
    /**
     * get the total number of Warnings
     * @return the total number of Warnings
     */
    public int  getWarnings(){
        if(isReplaceing()){
            if(this.selected==null){
                return 0;
            }
            return this.selected.getWarnings();
        }
        int result = 0;
        for(PageInfo p : keeptPages){
            result+=p.getWarnings();
        }
        return result;
    }

    /**
     * filter all the rules according to the user setting and update the ui
     * @param userInput the text the user put in the search box
     */
    public void filterAllRules(String userInput){
        if(this.keeptPages==null){
            ArrayList<PageInfo> pageInfos = new ArrayList<>();
            pageInfos.add(selected);
            this.toolWindowInfo.filterAllRules(userInput,pageInfos);
        }else{
            this.toolWindowInfo.filterAllRules(userInput,keeptPages);
        }
    }
}
